/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/irq.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>
#include <linux/dma-direction.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/sched.h>
#include <linux/mm.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <uapi/alga/amd/si/ioctl.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "fops.h"
#include "mmap.h"

long fops_dma_l2l(struct pci_dev *dev, struct si_dma *dma)
{
	struct vm_area_struct *src_vma;
	struct vm_area_struct *dst_vma;
	struct vma_private_data *src_vma_data;
	struct vma_private_data *dst_vma_data;
	u64 src_aperture_gpu_addr;
	u64 dst_aperture_gpu_addr;
	u64 src_gpu_addr;
	u64 dst_gpu_addr;
	struct si_dma_l2l *l2l;
	struct dmas_timeouts_info t_info;
	long r;

	r = 0;

	if (dma->type == SI_DMA_TO_DEVICE || dma->type == SI_DMA_FROM_DEVICE
						|| dma->type == SI_DMA_ON_HOST)
		down_read(&current->mm->mmap_sem);

	l2l = &dma->params.l2l;

	/* if the src addr is a cpu aperture addr... */
	if (dma->type == SI_DMA_TO_DEVICE || dma->type == SI_DMA_ON_HOST) {
		r = cpu_addr_to_aperture_gpu_addr(dev, &src_aperture_gpu_addr,
				(void __iomem *)l2l->src_addr, &src_vma);
		if (r != 0) {
			if (r == NO_VMA_FOUND)
				r = -EINVAL;
			/*
			 * XXX:a vma can be around without a gpu aperture,
			 * for instance after a suspend.
			 */
			else if (r == NO_BA_MAP_FOUND)
				r = 0; /* blank dma operation */
			goto unlock_mmap_sem;
		}
	}

	/* if the dst addr is a cpu aperture addr... */
	if (dma->type == SI_DMA_FROM_DEVICE || dma->type == SI_DMA_ON_HOST) {
		r = cpu_addr_to_aperture_gpu_addr(dev, &dst_aperture_gpu_addr,
				(void __iomem *)l2l->dst_addr, &dst_vma);
		if (r != 0) {
			if (r == NO_VMA_FOUND)
				r = -EINVAL;
			/*
			 * XXX:a vma can be around without a gpu aperture,
			 * for instance after a suspend.
			 */
			else if (r == NO_BA_MAP_FOUND)
				r = 0; /* blank dma operation */
			goto unlock_mmap_sem;
		}
	}

	switch (dma->dir) {
	case SI_DMA_FROM_DEVICE:
		dst_gpu_addr = dst_aperture_gpu_addr;
		src_gpu_addr = l2l->src_addr;
		break;
	case SI_DMA_TO_DEVICE:
		dst_gpu_addr = l2l->dst_addr;
		src_gpu_addr = src_aperture_gpu_addr;
		break;
	case SI_DMA_ON_DEVICE:
		dst_gpu_addr = l2l->dst_addr;
		src_gpu_addr = l2l->src_addr;
		break;
	case SI_DMA_ON_HOST:
		dst_gpu_addr = dst_aperture_gpu_addr;
		src_gpu_addr = src_aperture_gpu_addr;
		break;
	default:
		r = -EINVAL;
		goto unlock_mmap_sem;
	}

	if (dma->type == SI_DMA_TO_DEVICE || dma->type == SI_DMA_ON_HOST) {
		src_vma_data = src_vma->vm_private_data;
		dma_sync_sg_for_device(&dev->dev, src_vma_data->sg_tbl.sgl,
				src_vma_data->sg_tbl.nents, DMA_BIDIRECTIONAL);
	}

	memcpy(&t_info, &dma->t_info, sizeof(t_info));
	r = dmas_cpy(dev, dst_gpu_addr, src_gpu_addr, l2l->sz, t_info);
	if (r < 0) {
		if (r == -DMAS_RING_TIMEOUT)
			r = SI_RING_TIMEOUT;
		else if (r == -DMAS_FENCE_TIMEOUT)
			r = SI_FENCE_TIMEOUT;
		goto unlock_mmap_sem;
	}

	if (dma->type == SI_DMA_FROM_DEVICE || dma->type == SI_DMA_ON_HOST) {
		dst_vma_data = dst_vma->vm_private_data;
		dma_sync_sg_for_cpu(&dev->dev, dst_vma_data->sg_tbl.sgl,
				dst_vma_data->sg_tbl.nents, DMA_BIDIRECTIONAL);
	}

unlock_mmap_sem:
	if (dma->type == SI_DMA_TO_DEVICE || dma->type == SI_DMA_FROM_DEVICE
						|| dma->type == SI_DMA_ON_HOST)
		up_read(&current->mm->mmap_sem);
	return r;
}

/*
 * Direction is SI_DMA_ON_DEVICE only.
 * We don't want to fill the ram at the speed of the pcie bus, but at max
 * cpu<->ram speed.
 */
long fops_dma_u32_fill(struct pci_dev *dev, struct si_dma *dma)
{
	struct si_dma_u32_fill *u32_fill;
	struct dmas_timeouts_info t_info;
	long r;

	if (dma->dir != SI_DMA_ON_DEVICE)
		return -EINVAL;

	u32_fill = &dma->params.u32_fill;

	memcpy(&t_info, &dma->t_info, sizeof(t_info));
	r = dmas_u32_fill(dev, u32_fill->dst_addr, u32_fill->dws_n,
						u32_fill->constant, t_info);
	if (r < 0) {
		if (r == -DMAS_RING_TIMEOUT)
			r = SI_RING_TIMEOUT;
		else if (r == -DMAS_FENCE_TIMEOUT)
			r = SI_FENCE_TIMEOUT;
	}
	return r;
}
